﻿using System;
using System.Collections.Generic;
using System.Linq;
using Algoritmia.DataStructures.Maps;
using Algoritmia.Utils;

namespace Algoritmia.Problems.RegularExpressions
{
    public class NFASet
    {
        private readonly Func<IMap<string, DataStructures.Lists.IList<int>>> _createMapOfLists;
        private readonly IMap<int, IMap<string, DataStructures.Lists.IList<int>>> _transitions;
        private readonly Func<DataStructures.Lists.IList<int>> _createList;
        private int _counter;

        public NFASet(Func<IMap<int,IMap<string,DataStructures.Lists.IList<int>>>> createMap = null,
                      Func<IMap<string,DataStructures.Lists.IList<int>>> createMapOfLists = null,
                      Func<DataStructures.Lists.IList<int>> createList = null) 
        {
            createMap = createMap ?? (() => AlgoritmiaFactory.Maps<int, IMap<string, DataStructures.Lists.IList<int>>>.Default);
            _createMapOfLists = createMapOfLists ?? (() => AlgoritmiaFactory.Maps<string, DataStructures.Lists.IList<int>>.Default);
            _createList = createList ?? (() => AlgoritmiaFactory.Lists<int>.Default);
            _transitions = createMap();
            _counter = 0;
        }

        public class Nfa
        {
            private readonly NFASet _nfaSet;
            public int Start { get; private set; }
            public int End { get; private set; }

            public Nfa(NFASet nfaSet)
            {
                _nfaSet = nfaSet;
                Start = AddState();
                End = AddState();
            }

            public Nfa(NFASet nfaSet, int start, int end)
            {
                _nfaSet = nfaSet;
                Start = start;
                End = end;
            }   

            public int AddState()
            {
                return _nfaSet.AddState();
            }

            public void AddTransition(int tail, string symbol, int head)
            {
                _nfaSet.AddTransition(tail,symbol,head);
            }

            public IEnumerable<int> Destinations(int state, string symbol)
            {
                if ( _nfaSet._transitions.Contains(state) && _nfaSet._transitions[state].Contains(symbol) )
                {
                    return _nfaSet._transitions[state][symbol];
                }
                return Enumerable.Empty<int>();
            }

            public IEnumerable<string> OutputSymbols(int state)
            {
                if ( _nfaSet._transitions.Contains(state))
                {
                    return _nfaSet._transitions[state].Keys();
                }
                return Enumerable.Empty<string>();
            }   

            public Nfa Concat(Nfa other)
            {
                var newNfa = _nfaSet.CreateNfa(Start, other.End);
                AddTransition(End,"",other.Start);
                return newNfa;
            }

            public Nfa Union(Nfa other)
            {
                var newNfa = _nfaSet.CreateNfa();
                AddTransition(newNfa.Start,"",Start);
                AddTransition(newNfa.Start,"",other.Start);
                AddTransition(End,"",newNfa.End);
                AddTransition(other.End,"",newNfa.End);
                return newNfa;
            }

            public Nfa Close()
            {
                var newNfa = _nfaSet.CreateNfa();
                AddTransition(newNfa.Start,"",Start);
                AddTransition(End,"",newNfa.Start);
                AddTransition(newNfa.Start,"",newNfa.End);
                return newNfa;
            }
        }
    
        public Nfa CreateNfa()
        {
            return new Nfa(this);
        }

        public Nfa CreateNfa(int start, int end)
        {
            return new Nfa(this,start,end);
        }

        public int AddState()
        {
            _transitions[_counter] = _createMapOfLists();
            _counter++;
            return _counter-1;
        }

        public void AddTransition(int tail, string symbol, int head)
        {
            if ( !_transitions[tail].Contains(symbol))
            {
                _transitions[tail][symbol] = _createList();
            }
            _transitions[tail][symbol].Add(head);
        }
    }
}
